#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/resource.h>

#include "../comm/util.hpp"
#include "../comm/log.hpp"

namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;

    const int UMASK = 0;
    const int PERMISSION = 0644;

    #define FORK_ERROR -1
    #define OPEN_ERROR -2
    #define REPLACE_ERROR -3

    class Runner
    {
    public:
        Runner() = default;
        ~Runner() = default;

        static void SetProcLimit(int cpu_limit, int mem_limit)
        {
            struct rlimit cpu_rlim;
            cpu_rlim.rlim_cur = cpu_limit;
            cpu_rlim.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_CPU, &cpu_rlim);

            struct rlimit mem_rlim;
            mem_rlim.rlim_cur = mem_limit * 1024 * 1024;    // 单位MB
            mem_rlim.rlim_max = RLIM_INFINITY;  
            setrlimit(RLIMIT_AS, &mem_rlim);
        }

        /*****************************************
         * ret > 0:  程序异常，ret == 进程收到的信号编号
         * ret == 0: 正常运行完毕，结果保存在临时文件
         * ret < 0:  内部错误
         * 
         * cpu_limit: 程序运⾏时可使⽤的最⼤cpu资源上限
         * mem_limit: 程序运⾏时可使⽤的最⼤的内存⼤⼩(MB)
         *****************************************/  
        static int Run(const std::string& file_name, int cpu_limit, int mem_limit)
        {
            /********************************************
             * 程序运行结果：
             * 1. 代码运行结束，结果正确
             * 2. 代码运行结束，结果错误
             * 3. 代码运行异常
             * 但此处 Run 模块不考虑运行结束时的结果正确与否，
             * 这是评判机考虑的工作，此处只负责运行程序
             * 
             * 一共程序启动时，默认打开标准输入/输出/错误流
             * 标准输入：(暂不处理)
             * 标准输出：程序运行完成，输出结果
             * 标准错误，程序运行时发送错误的错误信息
             ********************************************/

            std::string _execute = PathUtil::Exe(file_name);
            std::string _stdin = PathUtil::Stdin(file_name);
            std::string _stdout = PathUtil::Stdout(file_name);
            std::string _stderr = PathUtil::Stderr(file_name);

            umask(UMASK);
            int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, PERMISSION);
            int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, PERMISSION);
            int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, PERMISSION);
            if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
            {
                LOG(ERROR) << "运行时打开标准文件失败\n";
                return OPEN_ERROR;
            }

            pid_t pid = fork();
            if(pid < 0)
            { 
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                LOG(INFO) << "创建子进程失败\n";
                return FORK_ERROR;
            }
            else if(pid == 0)
            {
                dup2(_stdin_fd, 0);
                dup2(_stdout_fd, 1);
                dup2(_stderr_fd, 2);

                SetProcLimit(cpu_limit, mem_limit);
                execl(_execute.c_str(), _execute.c_str(), nullptr);
                LOG(INFO) << "程序 " << _execute << " 替换失败 \n";
                exit(REPLACE_ERROR);
            }
            else
            {
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                int status = 0;     // 不关心结果正确与否，但需要关心是否运行异常
                waitpid(pid, &status, 0);
                LOG(INFO) << "运行完毕, info: " << (status & 0x7F) << "\n";
                return status & 0x7f;
            }
        }
    };
    
}